/*******************************************************************************
 * Copyright 2011 Towee.net
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/

package net.towee.server.rpc.servlet;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.towee.client.widgets.upload.UploadError;
import net.towee.model.repo.AttachmentIdentity;
import net.towee.model.repo.DocumentData;
import net.towee.model.repo.impl.AttachmentIdentityImpl;
import net.towee.model.utils.CharBase64;
import net.towee.server.authentication.SessionAuthManager;
import net.towee.server.exceptions.FileSizeLimitException;
import net.towee.server.persistence.AccountStore;
import net.towee.server.persistence.AttachmentStore;
import net.towee.server.persistence.AttachmentStore.AttachmentStreamer;
import net.towee.server.persistence.DocumentStore;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Inject;

/**
 * An attachment servlet is a simple servlet that serves up attachments from a
 * provided store. When atthachment uploaded it store id of one to session feild
 * of not used attachemnts, and when user put to Done button in DocumentCreator,
 * service get all attached ids and store it in DocumentData.
 */
public class AttachmentServlet extends HttpServlet {
	private static final Logger LOG = LoggerFactory.getLogger(AttachmentServlet.class);

	private final AttachmentStore attStore;
	private final DocumentStore docStore;
	private final AccountStore accountStore;

	@Inject
	private AttachmentServlet(AttachmentStore store, SessionAuthManager session, DocumentStore repoStore,
			AccountStore accStore) {
		this.attStore = store;
		this.docStore = repoStore;
		this.accountStore = accStore;
	}

	/**
	 * Upload data
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected void doPost(final HttpServletRequest request, final HttpServletResponse response)
			throws IOException {
		// Set response headers
		response.setContentType("text/plain");
		response.setCharacterEncoding("UTF-8");
		
		// Try to receive file
		try {
			List<FileItem> items = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);

			for (FileItem item : items) {
				if (item.getFieldName().equals("file")) {
					String fileName = FilenameUtils.getName(item.getName());
					InputStream content = item.getInputStream();
					
	        		// Attach file
	        		String fileId = attStore.storeAttachment( content, fileName );
	        		attStore.getAttachment(fileId);
	        		content.close();
					
	        		// Write file id
					response.getWriter().write(fileId);
					LOG.info("file uploaded "+fileName+"; "+fileId);
				}
			}
		} catch (FileUploadException e) {
			response.getWriter().write(UploadError.SERVER_ERROR.value());
		} catch (FileSizeLimitException e) {
			response.getWriter().write(UploadError.FILE_SIZE_LIMIT.value());
		}
	}
	
	@Override
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		// Get attachment id and document id
		if (request.getPathInfo() == null || request.getPathInfo().length() == 0) {
			response.sendError(HttpServletResponse.SC_NOT_FOUND);
			return;
		}

		// Get parts of request 
		String[] parts = request.getPathInfo().substring(1).split("/");
		if(parts.length < 3) {
			response.sendError(HttpServletResponse.SC_NOT_FOUND);
			return;
		}
		
		try {
			// Try get document
			DocumentData doc = docStore.getDocument(parts[1], "attach");
			
			// If we need to get full document
			if(parts[0].equals("full")) {
				// Increment all files
				incrementDownloading( doc.getAttachments().getAttachmentList() );
				
				// Stream result
				streamZippedFiles(response, doc.getAttachments().getAttachmentList());
			}	else if(parts[0].equals("multiple")){
				// Get list of files
				String[] files = new String( CharBase64.decodeWebSafe(parts[2]) ).split(",");
				
				// Increment  files
				incrementDownloading( files );
				
				// Send files
				streamZippedFiles(response, files);
			}	else if(parts[0].equals("single") && doc.getAttachments().contains(new AttachmentIdentityImpl(parts[2], null, 0, null))){
				// Increment file downloads
				incrementDownloading( parts[2] );
				
				// Send single file
				streamSingleFile(parts[2], response);
			}	else {
				response.sendError(HttpServletResponse.SC_NOT_FOUND);
				return;
			}
		} catch (Exception e) {
			LOG.info("Some error when recive attachments: ", e);
		}
	}

	/**
	 * Increment downloading counter for all files by
	 * given list of attachment identities
	 * @param ids
	 */
	private void incrementDownloading(List<AttachmentIdentity> idsList) {
		String[] ids = new String[ idsList.size() ];
		
		for(int i=0;i<idsList.size();i++)
			ids[i] = idsList.get(i).toString();
			
		incrementDownloading( ids );
	}

	/**
	 * Increment downloading counter for all files by
	 * given list of attachment ids
	 * @param ids
	 */
	private void incrementDownloading(String... ids) {
		for(String id : ids) 
			attStore.incrementDownloads(id);
	}
	
	/**
	 * Stream zipped files by given list of attachment identities
	 * @param response
	 * @param list
	 * @throws IOException 
	 */
	private void streamZippedFiles(HttpServletResponse response, List<AttachmentIdentity> list) throws IOException {
		String[] ids = new String[ list.size() ];
		
		for(int i=0;i<list.size();i++)
			ids[i] = list.get(i).toString();
			
		streamZippedFiles( response, ids );
	}
	
	/**
	 * Send zip archive with attachments
	 * @param response
	 * @param list
	 * @throws IOException
	 */
	private void streamZippedFiles(HttpServletResponse response, String... files) throws IOException {
		LOG.info("Send zipped attachments");
		
		// Create zip output stream
		ZipOutputStream output = new ZipOutputStream(response.getOutputStream());
		Map<String, Integer> names = new HashMap<String, Integer>();
		output.setLevel(3);
		output.setEncoding("CP866");
		
		// Response header with unknown zip size
		sendHeaders(response, "application/x-zip-compressed", -1, new Date().getTime());
		
		// Stream files
		for(String file : files){
			// Get attachment data from id
			AttachmentStreamer data = attStore.getAttachment( file );
			if(data == null) continue;

			// Check file name exists
			String fileName = data.getFileName();
			Integer coof = names.get( fileName );
			
			if(coof == null)  
				names.put(fileName, 1);
			else {
				int pointPos = fileName.lastIndexOf(".");
				
				if(pointPos < 0)
					fileName = fileName+"("+coof+")";
				else
					fileName = fileName.substring(0, pointPos)+"("+coof+")"+fileName.substring(pointPos);
				
				names.put(fileName, coof+1);
			}
			
			// Create zip entry in root of zip
		    ZipEntry entry = new ZipEntry( new String( fileName.getBytes(), "CP866" ) );
		    output.putNextEntry(entry); // Store entry
		    
		    // Write file
		    data.writeDataTo(output);
			LOG.info("\tZipped fetched attachment with id '" + file + "'");
		}
		
		// Close zipped stream
		output.close();
		LOG.info("\tAll attachments recived");
	}
	
	/**
	 * Stream single file without compression
	 * @param fileId
	 * @param response
	 * @throws IOException 
	 */
	private void streamSingleFile(String fileId, HttpServletResponse response) throws IOException {
		// Get attachment data from id
		AttachmentStreamer data = attStore.getAttachment( fileId );

		if (data == null) {
			response.sendError(HttpServletResponse.SC_NOT_FOUND);
			return;
		}
		
		// Send headers
		sendHeaders(response, data.getMimeType(), (int) data.getContentSize(), data.getLastModifiedDate().getTime());
		
		// Write data to client
		data.writeDataTo(response.getOutputStream());
		LOG.info("Fetched attachment with id '" + fileId + "'");
	}
	
	/**
	 * Send attachment header with spec mime, size and last mod date
	 * 
	 * @param response
	 * @param mime
	 * @param size
	 * @param lastMod
	 */
	private void sendHeaders(HttpServletResponse response, String mime, int size, long lastMod){
		response.setHeader("Content-Disposition", "attachment");
        response.setContentType( (mime != null) ? mime : "application/octet-stream" );
		response.setStatus(HttpServletResponse.SC_OK);
		response.setContentLength(size);
		response.setDateHeader("Last-Modified", lastMod);		
	}
}